﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Assertions;

namespace Nirvana
{

    public sealed class AssetBundleManager
    {
        private static Logger logger = LogSystem.GetLogger("AssetBundleManager");
        private AssetBundleLoadTaskMgr abLoadTaskMgr;
        private AssetBundleManifest abManifest;
        private float curTime;
        private int count;
        private int maxCount = 5;
        private string[] variants = new string[0];
        private LinkedList<AssetBundleItem> abItemList = new LinkedList<AssetBundleItem>();
        private Dictionary<string, AssetBundleItem> abItemMap = new Dictionary<string, AssetBundleItem>();
        private Queue<AssetBundleItem> abItemQueue = new Queue<AssetBundleItem>();

        private Dictionary<string, HashSet<string>> abhashMap = new Dictionary<string, HashSet<string>>();

        public int MaxCount
        {
            get { return maxCount; }
            set { maxCount = value; }
        }
        public string[] Variants
        {
            get
            {
                return variants;
            }
            set { variants = value; }
        }

        public AssetBundleManifest Manifest
        {
            get { return abManifest; }
            set { abManifest = value; }
        }


        public AssetBundleManager(AssetBundleLoadTaskMgr task)
        {
            this.abLoadTaskMgr = task;
        }

        public AssetBundleItem LoadAssetBundleItem(string path)
        {
            AssetBundleItem abitem;
            if(!this.abItemMap.TryGetValue(path,out abitem))
            {
                return null;
            }
            if (abitem.error != null) return abitem;
            if (abitem.assetbundle == null) return null;
            if (abitem.abItems != null)
            {
                foreach(var p in abitem.abItems)
                {
                    if (p != null)
                    {
                        if(p.error != null)
                        {
                            abitem.error = p.error;
                            return abitem;
                        }
                        if (p.assetbundle == null) return null;
                    }
                }
            }
            return abitem;
        }
        
        public bool LoadBundleAsync(string bundle)
        {
            var wait = this.abLoadTaskMgr.LoadBundleAsync(bundle, true);
            if (wait == null) return false;
            AssetBundleItem item = new AssetBundleItem(wait, "");
            item.Retain();
            this.abItemMap.Add(bundle, item);
            this.abItemList.AddLast(item);
            
            return true;
        }

        public bool LoadBundle(string bundle)
        {
            var wait = this.abLoadTaskMgr.LoadBundle(bundle);
            if (wait == null) return false;
            AssetBundleItem item = new AssetBundleItem(wait, "");
            item.Retain();
            this.abItemMap.Add(bundle, item);
            this.abItemList.AddLast(item);

            return true;
        }

        public AssetBundle GetAssetBundle(string bundle)
        {
            AssetBundleItem item = this.LoadAssetBundle(bundle);
            if (item != null) return item.assetbundle;
            return null;
        }
        public bool IsGetAssetBundle(string bundle,bool isSync)
        {
           return this.LoadAssetBundle(bundle,isSync) !=null;
        }

        public bool IsGetAssetBundle(string bundle)
        {
            AssetBundleItem item;
            if(this.abItemMap.TryGetValue(bundle,out item))
            {
                if (item.assetbundle != null) item.Release();
                return true;
            }
            return false;
        }

        public void RemoveAll()
        {
            if(this.abItemMap != null)
            {
                this.abItemMap.RemoveAll((bundle,item) => {
                    if (item.GetReferenceCount() > 0) return false;
                    item.UnLoad(true);
                    return true;
                });
            }
        }

        public void Remove(string bundle,bool isUnloadAll = false)
        {
            AssetBundleItem item;
            if(this.abItemMap.TryGetValue(bundle,out item))
            {
                item.UnLoad(isUnloadAll);
                this.abItemMap.Remove(bundle);
            }
        }

        public bool IsLoadSuccess()
        {
            foreach(var p in this.abItemList)
            {
                if (!p.GetAssetBundle()) return true;
            }
            return false;
        }

        public void Release()
        {
            foreach(var p in this.abItemMap)
            {
                if (p.Value.assetbundle != null) p.Value.assetbundle.Unload(false);
            }

            this.abItemList.Clear();
            this.abItemQueue.Clear();
            this.abItemMap.Clear();
        }

        public string[] GetAssets(string bundle)
        {
            Assert.IsNotNull<AssetBundleManifest>(this.abManifest);
            HashSet<string> hash = new HashSet<string>();
            this.AddAsset(bundle, hash);
            return hash.ToArray<string>();
        }

        public void AddBundleItem()
        {
            LinkedListNode<AssetBundleItem> next;
            for (LinkedListNode<AssetBundleItem> linkedListNode = this.abItemList.First; linkedListNode != null; linkedListNode = next)
            {
                next = linkedListNode.Next;
                AssetBundleItem value = linkedListNode.Value;
                if (value.GetAssetBundle())
                {
                    this.abItemList.Remove(linkedListNode);
                }
            }

            if ((double)(Time.time - this.curTime) >= 0.2f)
            {
                this.curTime = Time.time;
                this.count = 0;
                this.abItemMap.RemoveAll((bundle,item)=> {
                    if (this.count > 1) return false;
                    bool flag = item.IsUnload();
                    if (flag) this.count++;
                    return flag;
                });
            }
            this.AddItem();

        }

        private void AddAsset(string bundle,HashSet<string> hashs)
        {
            if (!hashs.Contains(bundle))
            {
                Hash128 hash = this.abManifest.GetAssetBundleHash(bundle);
                if (!this.abLoadTaskMgr.IsValidateCache(bundle, hash))
                {
                    hashs.Add(bundle);
                }
            }

            foreach(var p in this.abManifest.GetAllDependencies(bundle))
            {
                if (!hashs.Contains(p))
                {
                    Hash128 hash = this.abManifest.GetAssetBundleHash(p);
                    if (!this.abLoadTaskMgr.IsValidateCache(p, hash))
                    {
                        hashs.Add(p);
                    }
                }
            }

        }

        private AssetBundleItem LoadAssetBundle(string bundle)
        {
            AssetBundleItem item;
            if(this.abItemMap.TryGetValue(bundle,out item))
            {
                item.Retain();
                return item;
            }

            Assert.IsNotNull<AssetBundleManifest>(this.abManifest);
            Hash128 hash = this.abManifest.GetAssetBundleHash(bundle);
            if (!hash.isValid)
            {
                logger.LogError("The bundle {0} is not record in the manifest.", new object[]
                {
                    bundle
                });
                return null;
            }

            AssetBundle ab = this.abLoadTaskMgr.LoadFromFile(bundle, hash);
            if (ab == null) return null;
            item = new AssetBundleItem(ab, bundle);
            this.abItemMap.Add(bundle, item);

            string[] allDependeces = this.abManifest.GetAllDependencies(bundle);
            AssetBundleItem[] items = new AssetBundleItem[allDependeces.Length];
            for(int i = 0; i < allDependeces.Length; i++)
            {
                this.AddHash(allDependeces[i], bundle);
                items[i] = this.LoadAssetBundle(allDependeces[i]);
            }

            item.abItems = items;
            item.Retain();
            return item;
        }
        private AssetBundleItem LoadAssetBundle(string bundle,bool isSync)
        {
            AssetBundleItem item;
            if (this.abItemMap.TryGetValue(bundle, out item))
            {
                item.Retain();
                return item;
            }
            if (this.abManifest == null)
            {
                logger.LogError("The manifest is null, can not load {0}", new object[]
                {
                    bundle
                });
                return null;
            }

            Hash128 hash = this.abManifest.GetAssetBundleHash(bundle);
            if (!hash.isValid)
            {
                logger.LogError("The bundle {0} is not record in the manifest.", new object[]
                {
                    bundle
                });
                return null;
            }

            if (isSync)
            {
                WaitLoadAssetBundleAsync wait = this.abLoadTaskMgr.LoadBundleAsync(bundle, hash, isSync);
                if (wait == null || !string.IsNullOrEmpty(wait.Error))
                {
                    logger.LogError("Load bundle {0} with hash {1} is failed.", new object[]
                    {
                        bundle,
                        hash
                    });
                    return null;
                }
                item = new AssetBundleItem(wait, bundle);
                this.abItemMap.Add(bundle, item);
                this.abItemList.AddLast(item);
            }
            else
            {
                item = new AssetBundleItem(bundle);
                this.abItemMap.Add(bundle, item);
                this.abItemList.AddLast(item);
            }

            string[] allDependeces = this.abManifest.GetAllDependencies(bundle);
            AssetBundleItem[] items = new AssetBundleItem[allDependeces.Length];
            for (int i = 0; i < allDependeces.Length; i++)
            {
                this.AddHash(allDependeces[i], bundle);
                items[i] = this.LoadAssetBundle(allDependeces[i],isSync);
                if(items[i] == null)
                {
                    logger.LogError("Missing dependency bundle {0}.", new object[]
                    {
                        allDependeces[i]
                    });
                }
            }

            item.abItems = items;
            item.Retain();
            return item;
        }

        private void AddItem()
        {
            if (this.abItemQueue.Count <= 0) return;
            int num = this.maxCount + this.abItemQueue.Count / 10;
            int maxnum = num - this.abItemList.Count;
            if (maxnum <= 0) return;

            int i = 0;
            while(i< maxnum && this.abItemQueue.Count > 0)
            {
                AssetBundleItem u = this.abItemQueue.Dequeue();
                Hash128 assetBundleHash = this.abManifest.GetAssetBundleHash(u.AssetPath);
                WaitLoadAssetBundleAsync wait = this.abLoadTaskMgr.LoadBundleAsync(u.AssetPath, assetBundleHash, false);
                if (wait == null || !string.IsNullOrEmpty(wait.Error))
                {
                    logger.LogError("Load bundle {0} with hash {1} is failed.", new object[]
                    {
                        u.AssetPath,
                        assetBundleHash
                    });
                }
                else
                {
                    u.waitBundleYield = wait;
                    this.abItemList.AddLast(u);
                }
                i++;
            }

        }

        public void AddHash(string assetpath,string bundle)
        {
            HashSet<string> hash;
            if(!this.abhashMap.TryGetValue(assetpath,out hash))
            {
                hash = new HashSet<string>();
                this.abhashMap.Add(assetpath, hash);
            }
            hash.Add(bundle);
        }

        public string GetAllBundleInfo(bool isDetail)
        {
            List<object[]> _list = new List<object[]>();
            foreach(var p in this.abItemMap)
            {
                if (p.Value.GetReferenceCount() > 0)
                {
                    float t = Time.time - p.Value.RetainTime;
                    _list.Add(new object[]
                    {
                        p.Value.AssetPath,
                        p.Value.GetReferenceCount(),
                        t
                    });
                }
            }
            _list.Sort((a, b) => {
                float t1 = Convert.ToSingle(a[2]);
                float t2 = Convert.ToSingle(b[2]);
                return Convert.ToInt32(t2 - t1);
            });

            StringBuilder sb = new StringBuilder();
            if (!isDetail)
            {
                foreach( var p in _list)
                {
                    sb.Append(string.Format("{0}, ref={1}, last_use_time ={2}s\n", p[0], p[1], Convert.ToInt32(p[2])));
                }
                return sb.ToString();
            }

            foreach (var p in _list)
            {
                StringBuilder sb2 = new StringBuilder();
                HashSet<string> hashs = new HashSet<string>();
                this.FilterBundleInfo(p[0].ToString(), sb2, 0, hashs);

                if (sb2.Length > 0)
                {
                    sb.Append(sb2);
                    sb.Append("\n");
                }
            }
            return sb.ToString();

        }

        private void FilterBundleInfo(string bundle,StringBuilder sb,int length,HashSet<string> hashs)
        {
            for(int i = 0; i < length; i++)
            {
                sb.Append("    ");
            }
            if (bundle.StartsWith("[Asset]"))
            {
                sb.Append(bundle);
                sb.Append("\n");
                return;
            }
            HashSet<string> hs;
            if(!this.abhashMap.TryGetValue(bundle,out hs))
            {
                return;
            }

            AssetBundleItem item;
            if(!this.abItemMap.TryGetValue(bundle,out item) || item.GetReferenceCount() <= 0)
            {
                return;
            }

            hashs.Add(bundle);
            int t = Convert.ToInt32(Time.time - item.RetainTime);
            string arg = (length != 0) ? item.AssetPath : ("[AB]" + item.AssetPath);
            sb.Append(string.Format("{0}, ref={1}, last_use_time ={2}s\n", arg, item.GetReferenceCount(), t));
            length++;

            foreach(var s in hs)
            {
                if (hashs.Contains(s))
                {
                    Debug.LogErrorFormat("出现了包之间的互引用{0} => {1}", new object[]
                    {
                        bundle,
                        s
                    });
                }
                else
                {
                    this.FilterBundleInfo(s, sb, length, hashs);
                }
            }
            hashs.Remove(bundle);
        }

        public void DrawAssetBundles()
        {

        }
    }
}

